package com.cfpamf.ms.insur.wx.response;


import com.cfpamf.ms.insur.wx.util.CsvReaderUtil;
import com.google.common.collect.Lists;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.io.BufferedReader;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * 商户可以通过该接口下载历史交易清单。比如掉单、系统错误等导致商户侧和微信侧数据不一致，通过对账单核对后可校正支付状态。
 * <p>
 * 注意：
 * <p>
 * 1、微信侧未成功下单的交易不会出现在对账单中。支付成功后撤销的交易会出现在对账单中，跟原支付单订单号一致；
 * <p>
 * 2、微信在次日9点启动生成前一天的对账单，建议商户10点后再获取；
 * <p>
 * 3、对账单中涉及金额的字段单位为“元”。
 * <p>
 * 4、对账单接口只能下载三个月以内的账单。
 * <p>
 * 5、对账单是以商户号纬度来生成的，如一个商户号与多个appid有绑定关系，则使用其中任何一个appid都可以请求下载对账单。对账单中的appid取自交易时候提交的appid，与请求下载对账单时使用的appid无关。
 */
@Data
@Slf4j
public class WxPayDownloadBillResponse extends WxPayResponse {
    //格式化
    private static final DateTimeFormatter TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    //逗号分隔
    private static final String CSV_SEPARATOR = ",";

    /**
     * 参数前缀为标准键盘1左边键的字符
     */
    private static final String VALUE_PREFIX = "`";


    private static final String FIRST_HEAD_STR = "交易时间,公众账号ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,代金券或立减优惠金额,微信退款单号,商户退款单号,退款金额,代金券或立减优惠退款金额,退款类型,退款状态,商品名称,商户数据包,手续费,费率";
    private static final String LAST_HEAD_STR = "总交易单数,总交易额,总退款金额,总代金券或立减优惠退款金额,手续费总金额";

    private List<WxBillTrx> datas;
    /**
     * 总交易单数
     */
    private int trxCount;

    /**
     * 总交易额
     */
    private BigDecimal trxAmt;

    /**
     * 总退款金额
     */
    private BigDecimal refundAmt;

    /**
     * 总代金券或立减优惠退款金额
     */
    private BigDecimal ticketRefundAmt;

    /**
     * 手续费总金额
     */
    private BigDecimal poundageAmt;

    private boolean end;

    /**
     * 是否流水表头
     *
     * @param str
     * @return
     */
    private static boolean isHead(String str) {
        return StringUtils.startsWith(str, "交易时间,公众账号ID,");
    }

    /**
     * 是否统计的表头
     *
     * @param str
     * @return
     */
    private static boolean isSummaryHead(String str) {
        return StringUtils.startsWith(str, "总交易单数");
    }

    /**
     * 接口请求成功时，数据以文本表格的方式返回
     * 第一行为表头，根据请求下载的对账单类型不同而不同(由bill_type决定), 目前有：
     * 当日所有订单
     * 交易时间,公众账号ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额,代金券或立减优惠金额,微信退款单号,商户退款单号,退款金额, 代金券或立减优惠退款金额，退款类型，退款状态,商品名称,商户数据包,手续费,费率
     * 当日成功支付的订单
     * 交易时间,公众账号ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额, 代金券或立减优惠金额,商品名称,商户数据包,手续费,费率
     * 当日退款的订单
     * 交易时间,公众账号ID,商户号,子商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,总金额, 代金券或立减优惠金额,退款申请时间,退款成功时间,微信退款单号,商户退款单号,退款金额, 代金券或立减优惠退款金额,退款类型,退款状态,商品名称,商户数据包,手续费,费率
     * <p>
     * 从第二行起，为数据记录，各参数以逗号分隔，参数前增加`符号，为标准键盘1左边键的字符，字段顺序与表头一致。
     * <p>
     * 倒数第二行为订单统计标题，最后一行为统计数据
     *
     * @param datas
     * @param resp
     * @return
     */
    public static List<WxBillTrx> parse(List<String> datas, WxPayDownloadBillResponse resp) {
        //至少有两行头以及一行汇总数据
        if (CollectionUtils.isEmpty(datas) || datas.size() < 3) {
            return Collections.emptyList();
        }

        int size = datas.size();

        //第一行 和倒数第二行应该是固定的格式
        if (Objects.equals(datas.get(0), FIRST_HEAD_STR) && Objects.equals(datas.get(size - 2), LAST_HEAD_STR)) {
            throw new IllegalArgumentException("解析微信数据出错第一行以及倒数第二行格式错误");
        }

        String summary = datas.get(size - 1);
        String[] split = StringUtils.split(summary, CSV_SEPARATOR);
        //总交易单数,总交易额,总退款金额,总代金券或立减优惠退款金额,手续费总金额
//         `2,`0.02,`0.0,`0.0,`0
        Function<String, BigDecimal> amtParse = amtStr -> new BigDecimal(amtStr.replaceFirst("`", ""));

        resp.trxCount = Integer.parseInt(split[0].replaceFirst("`", ""));
        resp.trxAmt = amtParse.apply(split[1]);
        resp.refundAmt = amtParse.apply(split[2]);
        resp.ticketRefundAmt = amtParse.apply(split[3]);
        resp.poundageAmt = amtParse.apply(split[4]);
//         解析流水明细 跳过第一行 标题 以及最后两行的汇总数据
        List<WxBillTrx> trxes = Lists.newArrayListWithCapacity(size - 3);
        for (int i = 1; i < size - 2; i++) {
            String s = datas.get(i);
            //可以重写一个list add 到一定数量就去对数 改动就比较小了
            trxes.add(WxBillTrx.fromCsv(s));

        }
        resp.setDatas(trxes);
        return trxes;

    }

    public static void parsePage(BufferedReader br,
                                 Consumer<WxPayDownloadBillResponse> consumer) {
        //总交易单数,总交易额,总退款金额,总代金券或立减优惠退款金额,手续费总金额
//         `2,`0.02,`0.0,`0.0,`0
        CsvReaderUtil.pageReader(br, (list, isEnd) -> {

            WxPayDownloadBillResponse wxPayDownloadBillResponse = new WxPayDownloadBillResponse();
            Function<String, BigDecimal> amtParse = amtStr -> new BigDecimal(amtStr.replaceFirst("`", ""));
            List<WxBillTrx> trxes = Lists.newArrayListWithCapacity(list.size());
            for (int i = 0; i < list.size(); i++) {
                String content = list.get(i);
                //如果是标题数据
                if (isHead(content) || isSummaryHead(content))
                    continue;
                //最后一行 统计数据
                if (Objects.equals(Boolean.TRUE, isEnd) && i == list.size() - 1) {

                    String[] split = StringUtils.split(content, CSV_SEPARATOR);
                    wxPayDownloadBillResponse.trxCount = Integer.parseInt(split[0].replaceFirst("`", ""));
                    wxPayDownloadBillResponse.trxAmt = amtParse.apply(split[1]);
                    wxPayDownloadBillResponse.refundAmt = amtParse.apply(split[2]);
                    wxPayDownloadBillResponse.ticketRefundAmt = amtParse.apply(split[3]);
                    wxPayDownloadBillResponse.poundageAmt = amtParse.apply(split[4]);
                } else {
                    trxes.add(WxBillTrx.fromCsv(content));
                }
            }
            wxPayDownloadBillResponse.setDatas(trxes);
            wxPayDownloadBillResponse.setEnd(isEnd);
            consumer.accept(wxPayDownloadBillResponse);
        });
    }


    /**
     * 交易时间,公众账号ID,商户号,特约商户号,设备号,微信订单号,商户订单号,用户标识,交易类型,交易状态,付款银行,货币种类,应结订单金额,代金券金额,微信退款单号,商户退款单号,退款金额,充值券退款金额,退款类型,退款状态,商品名称,商户数据包,手续费,费率,订单金额,申请退款金额,费率备注
     */
    @Data
    @ApiModel("微信交易流水")
    public static class WxBillTrx {


        static WxBillTrx fromCsv(String content) {
            if (StringUtils.isBlank(content)) return null;
            // 将这个前缀去掉
            content = content.replaceAll("`", "");
            return CsvReaderUtil.parseCsv(content, WxBillTrx.class);
        }

        /**
         * 交易时间.
         */
        @ApiModelProperty("交易时间.")
        private LocalDateTime tradeTime;
        /**
         * 公众账号ID.
         */
        @ApiModelProperty("公众账号ID.")
        private String appId;
        /**
         * 商户号.
         */
        @ApiModelProperty("商户号.")
        private String mchId;
        /**
         * 特约商户号.
         */
        @ApiModelProperty("特约商户号.")
        private String subMchId;
        /**
         * 设备号.
         */
        @ApiModelProperty("设备号.")
        private String deviceInfo;
        /**
         * 微信订单号.
         */
        @ApiModelProperty("微信订单号.")
        private String transactionId;
        /**
         * 商户订单号.
         */
        @ApiModelProperty("商户订单号.")
        private String outTradeNo;
        /**
         * 用户标识.
         */
        @ApiModelProperty("用户标识.")
        private String openId;
        /**
         * 交易类型.
         */
        @ApiModelProperty("交易类型.")
        private String tradeType;
        /**
         * 交易状态.
         */
        @ApiModelProperty("交易状态.")
        private String tradeState;
        /**
         * 付款银行.
         */
        @ApiModelProperty("付款银行.")
        private String bankType;
        /**
         * 货币种类.
         */
        @ApiModelProperty("货币种类.")
        private String feeType;
        /**
         * 应结订单金额.
         */
        @ApiModelProperty("应结订单金额.")
        private BigDecimal totalFee;
        /**
         * 代金券金额.
         */
        @ApiModelProperty("代金券金额.")
        private BigDecimal couponFee;
        /**
         * 微信退款单号.
         */
        @ApiModelProperty("微信退款单号.")
        private String refundId;
        /**
         * 商户退款单号.
         */
        @ApiModelProperty("商户退款单号.")
        private String outRefundNo;
        /**
         * 退款金额.
         */
        @ApiModelProperty("退款金额.")
        private BigDecimal settlementRefundFee;
        /**
         * 充值券退款金额.
         */
        @ApiModelProperty("充值券退款金额.")
        private BigDecimal couponRefundFee;
        /**
         * 退款类型.
         */
        @ApiModelProperty("退款类型.")
        private String refundChannel;
        /**
         * 退款状态.
         */
        @ApiModelProperty("退款状态.")
        private String refundState;
        /**
         * 商品名称.
         */
        @ApiModelProperty("商品名称.")
        private String body;
        /**
         * 商户数据包.
         */
        @ApiModelProperty("商户数据包.")
        private String attach;
        /**
         * 手续费.
         */
        @ApiModelProperty("手续费.")
        private BigDecimal poundage;
        /**
         * 费率.
         */
        @ApiModelProperty("费率.")
        private String poundageRate;
        /**
         * 订单金额.
         */
        @ApiModelProperty("订单金额.")
        private BigDecimal totalAmount;
        /**
         * 申请退款金额.
         */
        @ApiModelProperty("申请退款金额.")
        private BigDecimal appliedRefundAmount;
        /**
         * 费率备注.
         */
        @ApiModelProperty("费率备注.")
        private String feeRemark;
        /**
         * 退款申请时间
         */
        @ApiModelProperty("退款申请时间")
        private LocalDateTime refundTime;
        /**
         * 退款成功时间
         */
        @ApiModelProperty("退款成功时间")
        private LocalDateTime refundSuccessTime;

    }
}
